import java.io.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 日程安排系统主类
 * 这是整个系统的核心类，包含了所有的业务逻辑和用户交互功能
 * 功能：添加、删除、修改、查询日程，时间冲突检测，文件持久化
 */
public class ScheduleManager {
    // 定义数据文件名常量，使用static final确保文件名不可更改
    // 所有的日程数据都将保存在这个文件中
    private static final String DATA_FILE = "schedules.txt";

    // 定义日期格式常量，统一整个系统的日期格式标准
    // 格式为：年-月-日 时:分，例如：2025-06-15 09:00
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");

    // 声明日程列表，用于在内存中存储所有的日程对象
    // 使用List接口提供灵活性，实际实现使用ArrayList
    private List<Schedule> schedules;

    // 声明键盘输入扫描器，用于读取用户从控制台输入的数据
    private Scanner scanner;

    // 构造方法：创建ScheduleManager对象时自动执行的初始化代码
    public ScheduleManager() {
        // 初始化日程列表为ArrayList，ArrayList是List接口的一个实现类
        // ArrayList底层使用数组实现，支持动态扩容，适合频繁的增删改查操作
        schedules = new ArrayList<>();

        // 初始化扫描器对象，System.in代表标准输入流（键盘输入）
        scanner = new Scanner(System.in);

        // 程序启动时自动从文件中加载已保存的日程数据
        // 这样可以实现数据的持久化，程序重启后数据不会丢失
        loadSchedulesFromFile();
    }

    /**
     * 内部静态类：日程类
     * 使用static关键字表示这是一个静态内部类，可以独立于外部类实例存在
     * 封装了单个日程的所有属性和相关操作方法
     */
    static class Schedule {
        // 日程的唯一标识符，用于区分不同的日程
        private String id;
        // 日程标题，简短描述日程的主要内容
        private String title;
        // 日程开始时间，使用Date类型存储精确的时间点
        private Date startTime;
        // 日程结束时间，必须晚于开始时间
        private Date endTime;
        // 日程详细内容，存储日程的具体描述信息
        private String content;
        // 参与人员列表，存储参加此日程的所有人员姓名
        private String participants;

        // 构造方法1：创建新日程时使用，自动生成唯一ID
        public Schedule(String title, Date startTime, Date endTime, String content, String participants) {
            // 使用UUID生成唯一标识符，并截取前8位作为日程ID
            // UUID.randomUUID()生成一个128位的随机唯一标识符
            // toString()将其转换为字符串格式
            // substring(0, 8)截取前8个字符，使ID简短易读
            this.id = UUID.randomUUID().toString().substring(0, 8);
            // 使用this关键字明确指向当前对象的属性，避免与参数名混淆
            this.title = title;
            this.startTime = startTime;
            this.endTime = endTime;
            this.content = content;
            this.participants = participants;
        }

        // 构造方法2：从文件加载数据时使用，使用已有的ID
        public Schedule(String id, String title, Date startTime, Date endTime, String content, String participants) {
            // 直接使用传入的ID，不重新生成
            this.id = id;
            this.title = title;
            this.startTime = startTime;
            this.endTime = endTime;
            this.content = content;
            this.participants = participants;
        }

        // Getter方法：提供对私有属性的只读访问
        // 遵循JavaBean规范，属性私有化，通过公共方法访问
        public String getId() {
            return id;
        } // 获取日程ID

        public String getTitle() {
            return title;
        } // 获取日程标题

        public void setTitle(String title) {
            this.title = title;
        } // 设置日程标题

        public Date getStartTime() {
            return startTime;
        } // 获取开始时间

        public void setStartTime(Date startTime) {
            this.startTime = startTime;
        } // 设置开始时间

        public Date getEndTime() {
            return endTime;
        } // 获取结束时间

        public void setEndTime(Date endTime) {
            this.endTime = endTime;
        } // 设置结束时间

        public String getContent() {
            return content;
        } // 获取日程内容

        public void setContent(String content) {
            this.content = content;
        } // 设置日程内容

        public String getParticipants() {
            return participants;
        } // 获取参与人员

        public void setParticipants(String participants) {
            this.participants = participants;
        } // 设置参与人员

        /**
         * 检查两个日程是否存在时间冲突的核心算法
         * 时间冲突的判断逻辑：两个时间段有重叠就是冲突
         * 
         * @param other 要比较的另一个日程对象
         * @return true表示有冲突，false表示无冲突
         */
        public boolean hasTimeConflict(Schedule other) {
            // 使用反向逻辑：先判断两个时间段不冲突的情况
            // 不冲突的情况：A的结束时间早于B的开始时间，或者B的结束时间早于A的开始时间
            // before()方法比较两个Date对象的先后关系
            // 使用逻辑非操作符(!)将"不冲突"转换为"冲突"
            return !(this.endTime.before(other.startTime) || other.endTime.before(this.startTime));
        }

        /**
         * 重写toString方法，定义对象的字符串表示形式
         * 当需要打印日程对象时，会自动调用这个方法
         * 
         * @return 格式化后的日程信息字符串
         */
        @Override
        public String toString() {
            // 使用String.format()方法进行字符串格式化
            // %s表示字符串占位符，按顺序替换为后面的参数
            // dateFormat.format()将Date对象转换为指定格式的字符串
            // \n表示换行符，使输出更加美观易读
            return String.format("[%s] %s (%s - %s)\n内容：%s\n参与人员：%s\n",
                    id, title, dateFormat.format(startTime), dateFormat.format(endTime), content, participants);
        }

        /**
         * 将日程对象转换为文件存储格式的字符串
         * 使用竖线(|)作为分隔符，便于后续解析
         * 
         * @return 用于文件存储的格式化字符串
         */
        public String toFileString() {
            // 将所有属性用竖线分隔，组成一行数据
            // 这种格式类似于CSV，但使用竖线而不是逗号，避免内容中的逗号造成解析错误
            return String.format("%s|%s|%s|%s|%s|%s",
                    id, title, dateFormat.format(startTime), dateFormat.format(endTime), content, participants);
        }

        /**
         * 静态方法：从文件字符串创建日程对象
         * static关键字表示这是类方法，不需要创建对象就可以调用
         * 
         * @param line 从文件中读取的一行数据
         * @return 解析后的Schedule对象，解析失败返回null
         * @throws ParseException 日期解析异常
         */
        public static Schedule fromFileString(String line) throws ParseException {
            // 使用split()方法按竖线分割字符串
            // \\|中的\\是转义字符，因为|在正则表达式中有特殊含义
            String[] parts = line.split("\\|");

            // 检查分割后的数组长度是否符合预期
            // 如果不是6个部分，说明数据格式有问题
            if (parts.length != 6)
                return null;

            // 按照预定义的顺序创建Schedule对象
            // parts[0]是ID，parts[1]是标题，以此类推
            // dateFormat.parse()将字符串转换为Date对象
            return new Schedule(
                    parts[0], // ID
                    parts[1], // 标题
                    dateFormat.parse(parts[2]), // 开始时间
                    dateFormat.parse(parts[3]), // 结束时间
                    parts[4], // 内容
                    parts[5] // 参与人员
            );
        }
    }

    /**
     * 程序主循环方法，控制整个程序的运行流程
     * 显示菜单、处理用户输入、调用相应功能
     */
    public void run() {
        // 打印欢迎信息，提升用户体验
        System.out.println("=== 欢迎使用日程安排系统 ===");

        // 使用无限循环保持程序运行，直到用户选择退出
        while (true) {
            // 显示主菜单选项
            showMenu();

            // 获取用户输入的选择项，并进行输入验证
            int choice = getIntInput("请选择操作");

            // 使用switch语句根据用户选择执行相应功能
            switch (choice) {
                case 1: // 用户选择添加日程
                    addSchedule();
                    break;
                case 2: // 用户选择查询日程
                    querySchedules();
                    break;
                case 3: // 用户选择修改日程
                    modifySchedule();
                    break;
                case 4: // 用户选择删除日程
                    deleteSchedule();
                    break;
                case 5: // 用户选择显示所有日程
                    showAllSchedules();
                    break;
                case 6: // 用户选择退出系统
                    // 退出前保存数据，确保数据不丢失
                    saveSchedulesToFile();
                    System.out.println("感谢使用日程安排系统！");
                    return; // 结束run方法，程序退出
                default: // 用户输入了无效选项
                    System.out.println("无效的选择，请重新输入！");
            }
        }
    }

    /**
     * 显示主菜单的方法
     * 将菜单显示逻辑封装为独立方法，提高代码的可维护性
     */
    private void showMenu() {
        // 使用println()方法打印菜单选项
        // \n表示换行，使菜单格式更美观
        System.out.println("\n=== 主菜单 ===");
        System.out.println("1. 添加日程");
        System.out.println("2. 查询日程");
        System.out.println("3. 修改日程");
        System.out.println("4. 删除日程");
        System.out.println("5. 显示所有日程");
        System.out.println("6. 退出系统");
        System.out.println("================");
    }

    /**
     * 添加日程的核心方法
     * 包含用户输入收集、数据验证、时间冲突检测等功能
     */
    private void addSchedule() {
        System.out.println("\n=== 添加日程 ===");

        // 收集日程标题信息
        String title = getStringInput("请输入日程标题");

        // 收集开始时间，使用专门的日期输入方法进行格式验证
        Date startTime = getDateInput("请输入开始时间 (格式: yyyy-MM-dd HH:mm)");
        // 如果日期输入失败（用户取消或格式错误），直接返回
        if (startTime == null)
            return;

        // 收集结束时间
        Date endTime = getDateInput("请输入结束时间 (格式: yyyy-MM-dd HH:mm)");
        if (endTime == null)
            return;

        // 验证时间逻辑：结束时间必须晚于开始时间
        // after()方法判断一个时间是否晚于另一个时间
        if (!endTime.after(startTime)) {
            System.out.println("错误：结束时间必须晚于开始时间！");
            return; // 验证失败，直接返回，不创建日程
        }

        // 收集日程详细内容
        String content = getStringInput("请输入日程内容");
        // 收集参与人员信息
        String participants = getStringInput("请输入参与人员");

        // 创建新的日程对象
        Schedule newSchedule = new Schedule(title, startTime, endTime, content, participants);

        // 检查新日程是否与现有日程存在时间冲突
        List<Schedule> conflicts = findConflictSchedules(newSchedule);

        // 如果发现冲突，提示用户并让用户决定是否继续
        if (!conflicts.isEmpty()) {
            System.out.println("警告：发现时间冲突的日程：");
            // 遍历所有冲突的日程，显示给用户
            for (Schedule conflict : conflicts) {
                System.out.println(conflict);
            }
            // 询问用户是否仍要添加此日程
            String confirm = getStringInput("是否仍要添加此日程？(y/n)");
            // toLowerCase()将输入转换为小写，startsWith()检查是否以指定字符开头
            if (!confirm.toLowerCase().startsWith("y")) {
                System.out.println("已取消添加日程。");
                return; // 用户取消，返回主菜单
            }
        }

        // 将新日程添加到列表中
        schedules.add(newSchedule);
        // 立即保存到文件，确保数据持久化
        saveSchedulesToFile();
        System.out.println("日程添加成功！ID: " + newSchedule.getId());
    }

    /**
     * 查询日程的主方法
     * 提供多种查询方式：按标题、按日期、按参与人员
     */
    private void querySchedules() {
        System.out.println("\n=== 查询日程 ===");
        // 显示查询方式选项
        System.out.println("1. 按标题查询");
        System.out.println("2. 按日期查询");
        System.out.println("3. 按参与人员查询");

        // 获取用户选择的查询方式
        int choice = getIntInput("请选择查询方式");

        // 根据用户选择调用相应的查询方法
        switch (choice) {
            case 1:
                queryByTitle(); // 按标题查询
                break;
            case 2:
                queryByDate(); // 按日期查询
                break;
            case 3:
                queryByParticipants(); // 按参与人员查询
                break;
            default:
                System.out.println("无效的选择！");
        }
    }

    /**
     * 按标题查询日程的方法
     * 使用模糊匹配，支持部分关键词搜索
     */
    private void queryByTitle() {
        // 获取用户输入的关键词
        String keyword = getStringInput("请输入标题关键词");
        // 创建结果列表存储符合条件的日程
        List<Schedule> results = new ArrayList<>();

        // 遍历所有日程进行匹配
        for (Schedule schedule : schedules) {
            // 使用contains()方法进行模糊匹配
            // toLowerCase()将字符串转换为小写，实现大小写不敏感的搜索
            if (schedule.getTitle().toLowerCase().contains(keyword.toLowerCase())) {
                results.add(schedule); // 匹配成功，添加到结果列表
            }
        }

        // 显示搜索结果
        displaySearchResults(results);
    }

    /**
     * 按日期查询日程的方法
     * 查找指定时间点包含在其时间范围内的所有日程
     */
    private void queryByDate() {
        // 获取用户输入的查询日期
        Date queryDate = getDateInput("请输入查询日期 (格式: yyyy-MM-dd HH:mm)");
        if (queryDate == null)
            return;

        List<Schedule> results = new ArrayList<>();

        // 遍历所有日程，检查查询时间是否在日程时间范围内
        for (Schedule schedule : schedules) {
            // 判断查询时间是否在[开始时间, 结束时间]范围内
            // !queryDate.before(startTime) 表示查询时间不早于开始时间
            // !queryDate.after(endTime) 表示查询时间不晚于结束时间
            if (!queryDate.before(schedule.getStartTime()) && !queryDate.after(schedule.getEndTime())) {
                results.add(schedule);
            }
        }

        displaySearchResults(results);
    }

    /**
     * 按参与人员查询日程的方法
     * 支持模糊匹配参与人员姓名
     */
    private void queryByParticipants() {
        String keyword = getStringInput("请输入参与人员关键词");
        List<Schedule> results = new ArrayList<>();

        // 在参与人员字段中搜索关键词
        for (Schedule schedule : schedules) {
            if (schedule.getParticipants().toLowerCase().contains(keyword.toLowerCase())) {
                results.add(schedule);
            }
        }

        displaySearchResults(results);
    }

    /**
     * 修改日程的方法
     * 允许用户修改日程的各个属性
     */
    private void modifySchedule() {
        System.out.println("\n=== 修改日程 ===");
        // 获取要修改的日程ID
        String id = getStringInput("请输入要修改的日程ID");

        // 根据ID查找对应的日程对象
        Schedule schedule = findScheduleById(id);
        if (schedule == null) {
            System.out.println("未找到指定ID的日程！");
            return;
        }

        // 显示当前日程信息，让用户了解现有内容
        System.out.println("当前日程信息：");
        System.out.println(schedule);

        System.out.println("请输入新信息（直接回车保持原值）：");

        // 使用带默认值的输入方法，用户可以选择保持原值
        String newTitle = getStringInputWithDefault("标题", schedule.getTitle());
        schedule.setTitle(newTitle);

        Date newStartTime = getDateInputWithDefault("开始时间", schedule.getStartTime());
        Date newEndTime = getDateInputWithDefault("结束时间", schedule.getEndTime());

        // 验证修改后的时间逻辑
        if (!newEndTime.after(newStartTime)) {
            System.out.println("错误：结束时间必须晚于开始时间！");
            return;
        }

        // 更新日程对象的属性
        schedule.setStartTime(newStartTime);
        schedule.setEndTime(newEndTime);

        String newContent = getStringInputWithDefault("内容", schedule.getContent());
        schedule.setContent(newContent);

        String newParticipants = getStringInputWithDefault("参与人员", schedule.getParticipants());
        schedule.setParticipants(newParticipants);

        // 保存修改后的数据
        saveSchedulesToFile();
        System.out.println("日程修改成功！");
    }

    /**
     * 删除日程的方法
     * 包含确认机制，防止误删
     */
    private void deleteSchedule() {
        System.out.println("\n=== 删除日程 ===");
        String id = getStringInput("请输入要删除的日程ID");

        // 查找要删除的日程
        Schedule schedule = findScheduleById(id);
        if (schedule == null) {
            System.out.println("未找到指定ID的日程！");
            return;
        }

        // 显示要删除的日程信息，让用户确认
        System.out.println("要删除的日程：");
        System.out.println(schedule);

        // 二次确认机制，防止误删重要数据
        String confirm = getStringInput("确认删除此日程吗？(y/n)");
        if (confirm.toLowerCase().startsWith("y")) {
            // 从列表中移除日程对象
            schedules.remove(schedule);
            // 保存更改到文件
            saveSchedulesToFile();
            System.out.println("日程删除成功！");
        } else {
            System.out.println("已取消删除。");
        }
    }

    /**
     * 显示所有日程的方法
     * 按时间顺序排序后显示
     */
    private void showAllSchedules() {
        System.out.println("\n=== 所有日程 ===");

        // 检查是否有日程数据
        if (schedules.isEmpty()) {
            System.out.println("暂无日程安排。");
            return;
        }

        // 按开始时间排序，使用Lambda表达式简化代码
        // (s1, s2) -> s1.getStartTime().compareTo(s2.getStartTime())
        // 是一个比较器，用于定义排序规则
        schedules.sort((s1, s2) -> s1.getStartTime().compareTo(s2.getStartTime()));

        // 遍历排序后的日程列表并显示
        for (Schedule schedule : schedules) {
            System.out.println(schedule);
            System.out.println("---"); // 分隔线，使输出更清晰
        }
    }

    /**
     * 查找与指定日程时间冲突的所有日程
     * 
     * @param newSchedule 要检查的新日程
     * @return 冲突的日程列表
     */
    private List<Schedule> findConflictSchedules(Schedule newSchedule) {
        List<Schedule> conflicts = new ArrayList<>();

        // 遍历现有日程，检查是否与新日程冲突
        for (Schedule schedule : schedules) {
            if (schedule.hasTimeConflict(newSchedule)) {
                conflicts.add(schedule);
            }
        }

        return conflicts;
    }

    /**
     * 根据ID查找日程对象
     * 
     * @param id 日程ID
     * @return 找到的日程对象，未找到返回null
     */
    private Schedule findScheduleById(String id) {
        // 遍历日程列表，查找匹配的ID
        for (Schedule schedule : schedules) {
            if (schedule.getId().equals(id)) {
                return schedule; // 找到匹配的日程，立即返回
            }
        }
        return null; // 未找到匹配的日程
    }

    /**
     * 显示搜索结果的方法
     * 
     * @param results 搜索结果列表
     */
    private void displaySearchResults(List<Schedule> results) {
        if (results.isEmpty()) {
            System.out.println("未找到匹配的日程。");
        } else {
            System.out.println("找到 " + results.size() + " 个匹配的日程：");
            // 显示所有搜索结果
            for (Schedule schedule : results) {
                System.out.println(schedule);
                System.out.println("---");
            }
        }
    }

    /**
     * 从文件加载日程数据的方法
     * 程序启动时自动调用，实现数据持久化
     */
    private void loadSchedulesFromFile() {
        // 使用try-with-resources语句自动管理文件资源
        // 无论是否发生异常，都会自动关闭文件流
        try (BufferedReader reader = new BufferedReader(new FileReader(DATA_FILE))) {
            String line;
            // 逐行读取文件内容
            while ((line = reader.readLine()) != null) {
                try {
                    // 尝试解析每一行数据为Schedule对象
                    Schedule schedule = Schedule.fromFileString(line);
                    if (schedule != null) {
                        schedules.add(schedule); // 成功解析，添加到列表
                    }
                } catch (ParseException e) {
                    // 解析失败，打印错误信息，但继续处理其他行
                    System.out.println("解析日程数据出错：" + line);
                }
            }
            System.out.println("成功加载 " + schedules.size() + " 个日程。");
        } catch (FileNotFoundException e) {
            // 文件不存在，这是正常情况（首次运行）
            System.out.println("数据文件不存在，将创建新文件。");
        } catch (IOException e) {
            // 其他IO异常
            System.out.println("读取文件时出错：" + e.getMessage());
        }
    }

    /**
     * 保存日程数据到文件的方法
     * 每次数据变更后调用，确保数据不丢失
     */
    private void saveSchedulesToFile() {
        // 使用try-with-resources自动管理文件资源
        try (PrintWriter writer = new PrintWriter(new FileWriter(DATA_FILE))) {
            // 遍历所有日程，将其转换为文件格式并写入
            for (Schedule schedule : schedules) {
                writer.println(schedule.toFileString());
            }
            System.out.println("数据已保存到文件。");
        } catch (IOException e) {
            // 保存失败，提示用户
            System.out.println("保存文件时出错：" + e.getMessage());
        }
    }

    // 以下是输入辅助方法，用于标准化用户输入处理

    /**
     * 获取字符串输入的方法
     * 
     * @param prompt 提示信息
     * @return 用户输入的字符串（去除首尾空格）
     */
    private String getStringInput(String prompt) {
        System.out.print(prompt + "：");
        // nextLine()读取整行输入，trim()去除首尾空格
        return scanner.nextLine().trim();
    }

    /**
     * 获取带默认值的字符串输入
     * 
     * @param prompt       提示信息
     * @param defaultValue 默认值
     * @return 用户输入或默认值
     */
    private String getStringInputWithDefault(String prompt, String defaultValue) {
        // 在提示信息中显示默认值
        System.out.print(prompt + " [" + defaultValue + "]：");
        String input = scanner.nextLine().trim();
        // 如果用户直接回车（输入为空），返回默认值
        return input.isEmpty() ? defaultValue : input;
    }

    /**
     * 获取整数输入的方法，包含输入验证
     * 
     * @param prompt 提示信息
     * @return 有效的整数输入
     */
    private int getIntInput(String prompt) {
        // 使用循环确保用户输入有效的整数
        while (true) {
            try {
                System.out.print(prompt + "：");
                // 尝试将输入转换为整数
                return Integer.parseInt(scanner.nextLine().trim());
            } catch (NumberFormatException e) {
                // 转换失败，提示用户重新输入
                System.out.println("请输入有效的数字！");
            }
        }
    }

    /**
     * 获取日期输入的方法，包含格式验证
     * 
     * @param prompt 提示信息
     * @return 有效的Date对象，失败返回null
     */
    private Date getDateInput(String prompt) {
        // 使用循环直到用户输入有效日期或选择取消
        while (true) {
            try {
                System.out.print(prompt + "：");
                String input = scanner.nextLine().trim();
                // 使用预定义的日期格式解析用户输入
                return dateFormat.parse(input);
            } catch (ParseException e) {
                // 解析失败，提示用户格式错误
                System.out.println("日期格式错误，请按 yyyy-MM-dd HH:mm 格式输入！");
                String retry = getStringInput("是否重新输入？(y/n)");
                if (!retry.toLowerCase().startsWith("y")) {
                    return null; // 用户选择取消
                }
            }
        }
    }

    /**
     * 获取带默认值的日期输入
     * 
     * @param prompt       提示信息
     * @param defaultValue 默认日期值
     * @return 用户输入的日期或默认值
     */
    private Date getDateInputWithDefault(String prompt, Date defaultValue) {
        // 显示默认值格式
        System.out.print(prompt + " [" + dateFormat.format(defaultValue) + "]：");
        String input = scanner.nextLine().trim();

        // 用户直接回车，使用默认值
        if (input.isEmpty()) {
            return defaultValue;
        }

        // 尝试解析用户输入
        try {
            return dateFormat.parse(input);
        } catch (ParseException e) {
            // 解析失败，使用默认值并提示用户
            System.out.println("日期格式错误，保持原值。");
            return defaultValue;
        }
    }

    /**
     * 程序入口方法
     * Java程序的标准入口点
     * 
     * @param args 命令行参数（本程序未使用）
     */
    public static void main(String[] args) {
        // 创建ScheduleManager对象
        ScheduleManager manager = new ScheduleManager();
        // 启动程序主循环
        manager.run();
    }
}